package pl.ydp.components.mathcell.mathcell.preprocessing
{
	import flash.geom.Point;
	
	import pl.ydp.components.mathcell.mathrecognizer.engine.Parameters;
	import pl.ydp.components.mathcell.mathrecognizer.geometry.Angle;
	import pl.ydp.components.mathcell.mathrecognizer.geometry.AngleTrend;
	import pl.ydp.components.mathcell.mathrecognizer.geometry.EuclideanVector;
	import pl.ydp.components.mathcell.mathrecognizer.geometry.Primitive;
	import pl.ydp.components.mathcell.mathrecognizer.geometry.classification.PrimitiveSubstanceType;
	
	/**
	 * This class is responsible for scaling the points
	 * input by the user. 
	 * 
	 * If the cell is smaller than the pointed size, the characters are
	 * scaled up in order to preserve the efficency of the character
	 * reconigtion engine.
	 * 
	 * After the character is recognized, the points are scaled down back
	 * to the original size of the cell.
	 * 
	 * This class provides not only the resize scaling, but also filtering,
	 * decimating and other linear and non-linear transformations. 
	 */
	public class ReScaler
	{
		public function ReScaler()
		{
		}
		
		public static function scalePointsUp(points:Array, scaleCharacter:Number, scaleData:ScaleCharacterData, scaleField:Number, margin:Number):Array{
			
			var arr:Number;
			var p:Number;
			
			var afterOffsetLeft:Number = scaleData.horizontal.offset*scaleField - scaleData.horizontal.size*(scaleCharacter-scaleField)/2 + margin;
			if (afterOffsetLeft < 0)
				afterOffsetLeft = margin;
			if (afterOffsetLeft + scaleData.horizontal.size*scaleCharacter > Parameters.CHAR_WIDTH)
				afterOffsetLeft = (Parameters.CHAR_WIDTH - scaleData.horizontal.size*scaleCharacter)/2;
			
			var afterOffsetTop:Number = scaleData.vertical.offset*scaleField - scaleData.vertical.size*(scaleCharacter-scaleField)/2 + margin;
			if (afterOffsetTop < 0)
				afterOffsetTop = margin;
			if (afterOffsetTop + scaleData.vertical.size*scaleCharacter > Parameters.CHAR_HEIGHT)
				afterOffsetTop = (Parameters.CHAR_HEIGHT - scaleData.vertical.size*scaleCharacter)/2;
			
			for (p = 0 ; p < points.length ; p ++){
				points[p] = decimatePoints(points[p]);
			}
			
			
			for (p = 0 ; p < points.length ; p ++){
				points[p] = filterPointsAll(points[p]);
			}
			
			
			var output:Array = new Array;
			
			for (arr = 0 ; arr < points.length ; arr ++ ){
				output.push(new Array);
				for (p = 0 ; p < points[arr].length ; p++){
					output[arr].push(
						new Point(
							(points[arr][p].x - scaleData.horizontal.offset)*scaleCharacter + afterOffsetLeft, 
							(points[arr][p].y - scaleData.vertical.offset)*scaleCharacter + afterOffsetTop
						)
					);
				} 
			}
			
			return output;
		}
		
		
		public static function scalePrimitivesDown(primitives:Array, inputWidth:Number, outputWidth:Number, offset:Number = 0):Array{
			
			var prim:Number;
			var v:Number;
			
			var output:Array = new Array;
			var tmpVectors:Array;
			var tmpSubstanceType:Number;
			
			var pointVectorBegin:Point;
			var pointVectorEnd:Point;
			
			for (prim = 0 ; prim < primitives.length ; prim ++ ){
				
				tmpVectors = new Array;
				
				for (v = 0 ; v < primitives[prim].vectors.length ; v ++){
					pointVectorBegin = new Point((primitives[prim].vectors[v].point.x*outputWidth/inputWidth)-offset, (primitives[prim].vectors[v].point.y*outputWidth/inputWidth));
					pointVectorEnd = primitives[prim].vectors[v].findEnd();
					pointVectorEnd = new Point((pointVectorEnd.x*outputWidth/inputWidth)-offset, (pointVectorEnd.y*outputWidth/inputWidth));
					tmpVectors.push(new EuclideanVector(pointVectorBegin, pointVectorEnd));
				}
				
				tmpSubstanceType = PrimitiveSubstanceType.fromPrimitiveType(primitives[prim].type);
				
				output.push(new Primitive(tmpVectors, tmpSubstanceType, primitives[prim].helix));
			}
			
			return output;
		}
		
		//------------------------------------------------------------------
		
		private static function decimatePoints(points:Array):Array{
			
			var pointsDecimated:Array = new Array;
			const SURVEY_WIDTH:Number = 3;
			
			pointsDecimated.push(points[0]);
			
			var x:Number = Parameters.MIN_PURE_VECTOR_LENGTH;
			
			var angle0:Number;
			var angle1:Number;
			
			for (var i:Number = 1 ; i < points.length - 1 ; i ++ ){
				angle0 = Angle.points2degrees( pointsDecimated[pointsDecimated.length-1] , points[i] );
				angle1 = Angle.points2degrees( points[i], points[i+1] );
				
				if (i == 56){
					var asd:Number = 10;
				}
				
				// eliminate stairs
				//
				//  *  *               *
				//     *  *      -->      *
				//        *  *               *
				//
				
				if (i < points.length - 2){
					if (Math.round(Angle.differenceAbs(angle0, angle1)) == AngleTrend.ANGLE_RIGHT   && 
						Math.round(Angle.differenceAbs(angle0, Angle.points2degrees( points[i+1], points[i+2] ) )) == AngleTrend.ANGLE_ZERO){
						continue;
					}
				}
				
				
				if (Point.distance(points[i], pointsDecimated[pointsDecimated.length-1]) > Parameters.MIN_PURE_VECTOR_LENGTH  ||  
					(Angle.differenceAbs(angle0, angle1) >= AngleTrend.THRESHOLD_CURVE-1)  ||  
					(Angle.differenceAbs(angle0, angle1) > AngleTrend.STILL)){
					pointsDecimated.push(points[i]);
				}
			}
			
			if (points.length > 1){
				pointsDecimated.push(points[points.length-1]);
			}
			
			//Debug.saveLog(pointsDecimated, "points (decimated)    in private function decimatePoints");
			
			return pointsDecimated;
		}
		
		
		//------------------------------------------------------------------
		private static function filterPointsAll(points:Array, saveDebug:Boolean = true):Array{
			
			const A:Number = 5;
			const B:Number = 2;
			const BASE:Number = 3;
			const SUM_AB:Number = A + B*2;	
			
			var p:Number;
			var pointNew:Point;	
			var pointsFiltered:Array = new Array;
			
			if (points.length < BASE){
				return points;
			}
			
			// beginning
			
			for (p = 0 ; p < Math.floor(BASE/2) ; p ++ ){
				pointsFiltered.push(points[p].clone());
			}
			
			// body
			
			for (p = Math.floor(BASE/2) ; p < points.length - Math.floor(BASE/2) ; p ++ ){
					pointNew = new Point();
					pointNew.x = ( A * points[p].x + B * (points[p-1].x + points[p+1].x)) / SUM_AB;
					pointNew.y = ( A * points[p].y + B * (points[p-1].y + points[p+1].y)) / SUM_AB;
					pointsFiltered.push(pointNew.clone()); 					
			}
			
			// ending
			
			for (p = points.length - Math.floor(BASE/2) ; p < points.length ; p ++ ){
				pointsFiltered.push(points[p].clone());
			}
			
			if (saveDebug){
				//Debug.saveLog(pointsFiltered, "points (filtered)    in private function filterPoints");	
			}
			
			return pointsFiltered;
			
		}
		

	}
}